Información general
Ámbito de aplicación de la norma
La Junta de Andalucía tiene como objetivo estratégico el despliegue de sus sistemas bajo un modelo de ejecución en nube híbrida, denominada Nube Corporativa de la Junta de Andalucía (NCJA), que proporcionará una infraestructura segura y escalable para maximizar los beneficios del modelo en nube: escalabilidad, flexibilidad y menores costes.
Por lo tanto, para facilitar el despliegue en nube, a todos los desarrollos de nuevas aplicaciones y nuevos sistemas de información que se creen en la Agencia Digital de Andalucía, se le debe exigir las siguientes normas generales Cloud-Native.
Descripción de la norma
Código fuente
El sistema debe gestionar adecuadamente su código fuente.
Eso incluye el control de cambios y el etiquetado de versiones completas.
Debe existir una correlación uno a uno entre el código base y el sistema o sus componentes.
Cada entidad a generar (como una librería o un componente web que haya que desplegar), debe tener su propio repositorio y evolucionar de forma independiente. Esto facilita la gestión de los cambios, ya que todos los cambios se rastrean y versionan en el sistema de control de versiones, teniendo su propio ciclo de vida.
Cada entidad, debe generar un solo proceso ejecutable, para facilitar la escalabilidad, portabilidad, monitorización y desacople de los componentes. Se entrará más en detalle sobre esto en el punto donde se traten los procesos.
Dependencias
En el contexto del desarrollo de software, una dependencia es una pieza de software en la que una aplicación depende para funcionar correctamente. Las dependencias pueden ser bibliotecas externas, marcos o herramientas que la aplicación necesita para ejecutarse.
No deben existir dependencias implícitas hacia librerías del sistema. Todas las dependencias serán explícitas.
Las dependencias deben declararse explícitamente en un archivo de manifiesto que utilizará el gestor de dependencias en cada caso. Esto permite el software pueda instalarse y configurarse fácilmente.
Utilizar solo las dependencias necesarias, eliminando dependencias con software no utilizado o con versiones incorrectas.
A veces, los problemas de dependencias se deben a que se está utilizando una versión incompatible de una dependencia.
Las dependencias deben estar aisladas en tiempo de ejecución, de forma que no afecten de forma implícita al resto del sistema.
Una forma de conseguirlo es mediante la instalación local, el uso de herramientas de aislamiento de dependencias durante la ejecución, o el uso de contenedores.
No deben existir dependencias de ninguna herramienta en el sistema operativo.
Configuración
No se permitirán artefactos que contengan configuración dependiente de entorno. La configuración deberá ser inyectada de forma previa a la ejecución.
La configuración de una aplicación es todo lo que puede variar entre despliegues (entornos de preproducción, producción, desarrollo, etc), lo cual incluye:
- URL de recursos
- Credenciales
- Valores que pueden cambiar entre entornos, como el nombre del servicio
Mecanismos permitidos para realizar la configuración:
- Utilizar variables de entorno: Las variables de entorno son una forma sencilla y eficiente de proporcionar configuración a un contenedor o máquina virtual.
- Utilizar volúmenes externos: Se pueden utilizar volúmenes externos para proporcionar configuración, como archivos de configuración o certificados.
- Utilizar herramientas de configuración: Se pueden utilizar herramientas de configuración, como HashiCorp Consul o Spring Cloud Config, para proporcionar configuración en tiempo de ejecución de manera dinámica y descentralizada. Estas herramientas suelen proporcionar mecanismos de versión y de propiedad de la configuración que facilitan la gestión de la configuración a lo largo del tiempo.
Backing services
Un backing service es cualquier recurso que la aplicación puede consumir a través de la red como parte de su funcionamiento habitual. Entre otros ejemplos, podemos encontrar bases de datos (como Oracle o MariaDB), los sistemas de mensajería y de colas (como RabbitMQ o Beanstalkd), los servicios SMTP de email (como Postfix), y los sistemas de cache (como Memcached).
Los recursos pueden ser locales o externos (de la organización o de terceros).
Los recursos serán accedidos sin distinguir si son locales y de terceros.
El acceso a los recursos se hará mediante un localizador (URL o similar) y unas credenciales, cuando sea necesario. Esta información, que forma parte de la configuración, será almacenada fuera del código.
A continuación, se tratan algunos casos especiales para los que se definen la siguientes normas expresas:
a) Integración con sistema de archivos
No se debe utilizar un sistema de archivos local ya que la información puede ser eliminada entre reinicios.
La persistencia de la información en ficheros se realizará externamente, existiendo varias opciones:
- Almacenar los datos en un volumen externo: Un volumen externo es una unidad de almacenamiento que se puede conectar a un contenedor o instancia de MV desde el exterior. Los volúmenes externos pueden ser almacenamientos en la nube, un volumen NFS, o cualquier otro tipo de almacenamiento “acoplable”.
- Utilizar un sistema de almacenamiento distribuido como CEPH y una librería compatible con el lenguaje utilizado para interactuar.
b) Integración con otros sistemas
No está permitida la integración entre sistemas basada en el acceso directo a bases de datos o a ficheros compartidos.
La integración con otros sistemas se realizará mediante el uso de APIs.
Las APIs son un conjunto de protocolos y herramientas que permiten que diferentes aplicaciones y servicios se comuniquen y compartan datos de manera sencilla y segura. Al utilizar APIs, no es necesario conocer los detalles de implementación de las aplicaciones o servicios que se están conectando, lo que hace que sea más fácil integrar y utilizar nuevos productos y servicios.
Por tanto, las únicas formas permitidas de integración con terceros son a través del uso de APIs (en REST / SOAP o similares, como XML/RPC etc). Cualquier otra forma de interoperabilidad, como la integración a través de bases de datos o el intercambio de archivos, está expresamente prohibida.
Construir, distribuir y ejecutar
Se deben de separar completamente las distintas etapas del ciclo de vida del software:
- La etapa de construcción es una transformación que convierte un repositorio de código en un paquete ejecutable llamado construcción (una “build”). En la etapa de construcción se traen todas las dependencias y se compilan los binarios y las herramientas usando una versión concreta del código correspondiente a un commit especificado por el proceso de despliegue.
- En la fase de distribución se usa la construcción creada en la fase de construcción y se combina con la configuración del despliegue actual. Por tanto, la distribución resultante contiene tanto la construcción como la configuración y está lista para ejecutarse inmediatamente en el entorno de ejecución.
- La fase de ejecución (también conocida como “runtime”) ejecuta la aplicación en el entorno de ejecución, lanzando un conjunto de procesos de una distribución concreta de la aplicación.
Procesos
Un sistema se ejecuta como uno o más procesos en el entorno de ejecución, desde el caso en el que tenemos un proceso único en un entorno de desarrollo a un entorno de producción con múltiples tipos de procesos, a su vez con múltiples instancias que se ejecutan de forma concurrente.
Los procesos deben ser sin estado (no dependen del resultado de peticiones previas) y “share nothing” (los procesos no comparten memoria ni recursos).
Cualquier información que necesite persistencia se debe almacenar en un “backing service” con estado, habitualmente una base de datos. No se puede confiar en la persistencia en memoria o en sistema de archivos local, dado que tienen un carácter volátil.
Por tanto, no se utilizarán sticky sessions, ni ningún otro mecanismo que implique que ciertas peticiones tengan que ser atendidas por un mismo proceso (como en el caso de las sesiones de usuario en http).
A título ilustrativo, para solucionar el problema de las sticky-sessions se podrá optar por:
- Utilizar mecanismos para el cacheo de la información de sesión en servidor, que haga transparente la instancia del proceso que atiende al cliente.
- Utilizar mecanismos para el almacenamiento en cliente, de forma que la interacción del mismo con el servidor incluya toda la información de estado necesaria. Este mecanismo debe garantizar una adecuada confidencialidad de la información almacenada en el cliente.
Servicios autocontenidos (Asignación de puertos)
El sistema debe ser auto-contenido y no depender de un servidor web en ejecución para crear un servicio web público.
Las aplicaciones cloud-native no dependen (o corren en) servidores de aplicaciones externos, sino que lo llevan incluidos, normalmente gracias a librerías que implementan esa funcionalidad.
Concurrencia
Las aplicaciones deben ser diseñadas para poder ser divididas en componentes ejecutables de manera independiente, lo que permite aumentar el rendimiento y la capacidad de la aplicación al aumentar el número de componentes ejecutables.
No se permitirá el despliegue de aplicaciones que presenten problemas a la hora de utilizar dos o más réplicas.
Por tanto, deben diseñarse para manejar la concurrencia de manera eficiente y escalable.
Los problemas de concurrencia pueden ser un problema común, especialmente al desplegar sistemas en contenedores. Estos problemas pueden ser causados por varias razones, tales como:
- Acceso concurrente a recursos compartidos: Si varias instancias de una aplicación o servicio intentan acceder al mismo tiempo a un recurso compartido, como un archivo o una base de datos, puede haber conflictos que causen problemas de concurrencia.
- Sesiones compartidas: Si varias instancias de una aplicación o servicio comparten la misma sesión, puede haber problemas de concurrencia si dos o más instancias intentan acceder al mismo tiempo a los datos de la sesión.
- Acceso concurrente a caché: Si varias instancias de una aplicación o servicio intentan acceder al mismo tiempo a una caché compartida, puede haber conflictos que causen problemas de concurrencia.
Es importante tener en cuenta que no existe una solución única para todos los problemas de concurrencia y que la mejor opción dependerá cada caso. Algunos ejemplos de soluciones válidas:
- Utilizar mecanismos de bloqueo para proteger el acceso a recursos compartidos: Por ejemplo, puedes utilizar semáforos o bloqueos de base de datos para proteger el acceso a archivos o bases de datos compartidos.
- Utilizar una base de datos distribuida: Una base de datos distribuida puede permitir el acceso concurrente a los datos de manera más eficiente y segura, lo que puede ayudar a solucionar los problemas de concurrencia.
- Utilizar una caché distribuida: Una caché distribuida puede permitir el acceso concurrente a los datos de la caché de manera más eficiente y segura, lo que puede ayudar a solucionar los problemas de concurrencia.
Desechabilidad
Los procesos son desechables, lo que significa que pueden iniciarse o finalizarse en el momento que sea necesario.
Esto permite un escalado rápido y flexible, un despliegue rápido del código y de los cambios de las configuraciones
Los procesos deberían intentar conseguir minimizar el tiempo de arranque
En el caso de implementaciones basadas en contenedores, se deberán seguir las recomendaciones indicadas en las buenas prácticas construcción imágenes, que estén vigentes en cada momento, para generar imágenes lo más livianas posibles.
Los procesos deben gestionar adecuadamente las señales de finalización de procesos.
De esta forma, puede finalizarse de forma ordenada el trabajo en curso o no finalizado. Eso incluye devolver a una cola el trabajo no atendido o esperar a la finalización de una tarea, dentro de un time-out establecido.
Los procesos deben estar preparados para las finalizaciones inesperadas.
Se tomarán las medidas oportunas, dependiendo de las consecuencias que pueda tener la interrupción.
Para lograr procesos desechables y ligeros se usarán las técnicas y componentes de arquitectura que en cada caso estén disponibles.
Igualdad entre desarrollo y producción
En la medida de lo posible, se buscará la igualdad entre los entornos de desarrollo y producción.
Esto permite a los desarrolladores probar el código en un entorno que se asemeja lo más posible al entorno de producción, lo que reduce el riesgo de encontrar errores o problemas en fases tempranas.
Para cumplir con este factor, es importante tener en cuenta lo siguiente:
- El software desplegado en los entornos de desarrollo y producción deben ser lo más similares posible: hay que minimizar los tiempos de despliegue de nuevas características
- Se utilizarán las mismas herramientas y tecnologías en ambos entornos.
- Los procesos de despliegue deben ser iguales o lo más parecido posible.
- Se utilizarán pruebas automatizadas para asegurar la calidad del código.
Tener una equivalencia desarrollo-producción, puede resultar complicado por razones obvias, pero si podemos intentar utilizar herramientas similares, por ejemplo:
- Si se conoce que la aplicación, cuando se encuentre en producción va a atacar a una BBDD Oracle, no se debe de utilizar durante el desarrollo una MySQL o una H2 embebida.
- Si se conoce que la aplicación se va a desplegar en un orquestador de K8s, no se debe desplegar en un contenedor local y se debe de intentar montarse en un clúster local de minikube.
Historiales (Logs)
Los logs se escribirán en la salida estándar o la salida estándar de error.
Delegando la recopilación de la información en los mecanismos de recolección de eventos de log.
Se normalizará el formato de los mensajes, con el fin de facilitar su análisis.
Los logs a generar deben incluir suficiente información para facilitar la depuración de problemas y el monitoreo del rendimiento de la aplicación. El formato que se utilizará para los mismos será el siguiente:
YYYY-MM-DD | HH:mm:ss.SSS | LOGLEVEL | ApplicationName | Message
- YYYY-MM-DD HH:mm:ss.SSS: Corresponde a la fecha del systema en el momento que se genera el log.
- LOGLEVEL:
- ERROR: Se utiliza en mensajes de error de la aplicación que se desea guardar, estos eventos afectan al programa, pero lo dejan seguir funcionando, como por ejemplo que algún parámetro de configuración no es correcto y se carga el parámetro por defecto.
- WARN: Se utiliza para mensajes de alerta sobre eventos que se desea mantener constancia, pero que no afectan al correcto funcionamiento del programa.
- INFO: Se utiliza para mensajes similares al modo "verbose" en otras aplicaciones.
- DEBUG: Se utiliza para escribir mensajes de depuración. Este nivel no debe estar activado cuando la aplicación se encuentre en producción.
- TRACE: Se utiliza para mostrar mensajes con un mayor nivel de detalle que debug.
- ApplicationName: Es el nombre del sistema y componente al que pertenece el log
- Message: Mensaje con suficiente nivel de detalle para poder diagnosticar problemas o tener información de las acciones que se están realizando en la aplicación.
Procesos de administración o mantenimiento
Ejecutar las tareas de gestión/administración como procesos que solo se ejecutan una vez.
Se trata de procesos pensados para ejecutarse solo una vez, con una finalidad concreta, por ejemplo:
- Ejecutar migraciones de las bases de datos (e.g. manage.py migrate de Django, rake db:migrate de Rails).
- Ejecutar una consola (también conocidas como REPL) para ejecutar código arbitrario o inspeccionar los modelos de la aplicación en una base de datos con datos reales.
- Ejecutar scripts incluidos en el repositorio de la aplicación (e.g. php scripts/fix_bad_records.php).
Ejecutar los procesos de mantenimiento desde un entorno idéntico al de producción.
Asociar los procesos de administración o mantenimiento a una distribución concreta, usando el mismo código y la misma configuración que cualquier otro proceso que ejecuta esa distribución.
Eso implica que, el código de administración y mantenimiento se debe gestionar como el código de la aplicación, para evitar problemas de sincronización.
Versiones
Versión actual: v01r00
- Fecha de aprobación: 10/03/2023.